package com.intellect.syncproject;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;

public class SimpleSynch {

	XMLConfiguration config;
	File sourceDir;
	File targetDir;
	SynchListREFilter listFilter = null;
	
	public File getSourceDir() {
		return sourceDir;
	}

	public void setSourceDir(File sourceDir) {
		this.sourceDir = sourceDir;
	}

	public File getTargetDir() {
		return targetDir;
	}

	public void setTargetDir(File targetDir) {
		this.targetDir = targetDir;
	}

	@SuppressWarnings("unchecked")
	public void init(String fn) throws ConfigurationException {
		config = new XMLConfiguration(fn);
		
		sourceDir = new File(config.getString("sourceDir"));
		targetDir = new File(config.getString("targetDir"));

		List<String> fileExcludeCriteria = config.getList("fileCriteria.exclude");
		List<Pattern> fileExclREList = null;
		if( fileExcludeCriteria != null ) {
			fileExclREList = new ArrayList<Pattern>();
			for( String fileExcl : fileExcludeCriteria ) {
				Pattern exclPattern = Pattern.compile(fileExcl);
				fileExclREList.add(exclPattern);
			}
		}

		List<String> dirExcludeCriteria = config.getList("dirCriteria.exclude");
		List<Pattern> dirExclREList = null;
		if( dirExcludeCriteria != null ) {
			dirExclREList = new ArrayList<Pattern>();			
			for( String dirExcl : dirExcludeCriteria ) {
				Pattern exclPattern = Pattern.compile(dirExcl);
				dirExclREList.add(exclPattern);
			}
		}
		
		listFilter = new SynchListREFilter(dirExclREList, fileExclREList);
		
	}
	
	/**
	 * Performs a source -> target copy. Here are the rules:
	 * 1) Only the target will be modified.  
	 * 2) If a target file exists, but no source file, delete target.
	 * 3) If a file exists on both source and target, if source is newer than target, copy it
	 * 4) If file exists on source but not target, copy to target.
	 * 
	 * @return
	 * @throws Exception 
	 */
	public List<SynchWorkItem> buildWorkListSourceToTarget(int srcRelativePathLen, int targetRelativePathLen) throws Exception {
		List<File> sourceFiles = listFilter.getListSortedByCanonicalName(sourceDir);
		List<File> targetFiles = listFilter.getListSortedByCanonicalName(targetDir);
		int totalFiles = Math.max(sourceFiles.size(), targetFiles.size());

		if( totalFiles == 0 ) {
			System.out.println("Source and target file list is empty!  -- Nothing to do :(");
			return null;
		}
		
		List<SynchWorkItem> workItems = new ArrayList<SynchWorkItem>();
		
		Iterator<File> sourceIter = sourceFiles.iterator();
		Iterator<File> targetIter = targetFiles.iterator();

		File sourceFile = (sourceIter.hasNext()) ? sourceIter.next() : null;
		File targetFile = (targetIter.hasNext() ) ? targetIter.next() : null;

		
		for( int ctr = 0; ctr < totalFiles; ctr++) {
			if( ctr % 50 == 0 ) {
				System.out.printf("Compared files: %d of %d %n", ctr, totalFiles);
			}
			
			if( sourceFile == null && targetFile == null ) {
				System.out.println("Done matching...");
				break;
			}
			if( sourceFile == null ) {
				// If this weren't a simple synch, we'd copy target to source here!  but we'll just skip it for now
				targetFile = (targetIter.hasNext() ) ? targetIter.next() : null;
			}  else if( targetFile == null ) {
				workItems.add(new SynchWorkItem(sourceFile, targetFile, SynchWorkItem.ActionType.create));
				sourceFile = (sourceIter.hasNext()) ? sourceIter.next() : null;
			} else {
				// Now we need to do a compare...
				String sourceName = sourceFile.getCanonicalPath();
				String targetName = targetFile.getCanonicalPath();
				
				String srcRelName = StringUtils.right(sourceName, sourceName.length() - srcRelativePathLen);
				String tgtRelName = StringUtils.right(targetName, targetName.length() - targetRelativePathLen);

				System.out.printf("Comparing: %s with %s%n", srcRelName, tgtRelName);
				int comp = srcRelName.compareTo(tgtRelName);
				
				if( comp == 0 ) {
					// Sanity check: If both names are equal, they must be either both files or both directories...
					if( sourceFile.isDirectory() != targetFile.isDirectory() ) {
						throw new Exception(
								String.format("The files/directories: %s %s are not of the same type of object!!  Sanity check failed!",
										sourceName, targetName));
					}
					
					// If they're equal, skip them if they're a directory...don't need to check attributes
					if( !sourceFile.isDirectory() ) {
						if( compareSpecs(sourceFile, targetFile) > 0 ) {
							workItems.add(new SynchWorkItem(sourceFile, targetFile, SynchWorkItem.ActionType.replace));
						} else if( compareSpecs(sourceFile, targetFile) < 0 ) {
							// Target is newer (for now, no action taken!)
						}
					}

					targetFile = (targetIter.hasNext() ) ? targetIter.next() : null;
					sourceFile = (sourceIter.hasNext()) ? sourceIter.next() : null;
				} else if( comp > 0 ) {
				    // Target is greater than the source, so the source contains a file that the source doesn't...
					workItems.add(new SynchWorkItem(sourceFile, SynchWorkItem.ActionType.create, true));
					sourceFile = (sourceIter.hasNext()) ? sourceIter.next() : null;
				} else if( comp < 0 ) {
					// Target is less than source - delete it!
					workItems.add(new SynchWorkItem(targetFile, SynchWorkItem.ActionType.delete, true));
					targetFile = (targetIter.hasNext() ) ? targetIter.next() : null;
				}
			}
		}
		
		return workItems;
	}
	
	public int compareSpecs(File sourceFile, File targetFile) {
		// If they're both directories, and they both exist...leave 'em alone!
		if( sourceFile.isDirectory() && targetFile.isDirectory() ) {
			return 0;
		}
		
		if( sourceFile.lastModified() == targetFile.lastModified() ) {
			return 0;
		}
		
		if( sourceFile.lastModified() > targetFile.lastModified() ) {
			// Source is newer
			return 1;
		}
		
		// Target is newer
		return -1;
	}
	
	public void saveWorkList(String fn, List<SynchWorkItem> workItems) throws IOException {
		for( SynchWorkItem item : workItems ) {
			FileUtils.writeStringToFile(new File(fn), item.toSaveString());
		}
	}
	
	public static void main(String[] args) throws ConfigurationException, IOException {
		
		SimpleSynch ss = new SimpleSynch();
		ss.init("CopyCriteria.xml");
		
		List<SynchWorkItem> workItems;
		try {
			workItems = ss.buildWorkListSourceToTarget(ss.getSourceDir().getCanonicalPath().length(), ss.getTargetDir().getCanonicalPath().length());
			if( workItems != null && workItems.size() > 0 ) {
				ss.saveWorkList("./workList.txt", workItems);
				System.out.println("Worklist has been saved!!!");
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}
